--[[
  Vendetta Online plugin for gathering various information
  minerals, trade goods, etc
]]

dofile("http.lua")
dofile("tcpsock.lua")
dofile("util.lua")

-- ------------------------------------------------------------------------
-- CONFIGURATION SECTION
-- ------------------------------------------------------------------------
-- getting things from config file
-- gkini.ReadString("Vendetta", "Username", "dude") -> "hans" -- get the default username from the config file
local loglevel = gkini.ReadInt("CGCC", "loglevel", 1)
local dburl = gkini.ReadString("CGCC", "dburl", "http://localhost/cgi-bin/db.pl")

-- ------------------------------------------------------------------------
-- UTILITY ROUTINES
-- ------------------------------------------------------------------------
-- this order is important!
orelist = {"Aquean", "Silicate", "Carbonic", "Ferric", "VanAzek", "Ishik",
	   "Xithricite", "Lanthanic", "Denic", "Pyronic", "Apicene",
	   "Pentric", "Heliocene"};
-- debug logging
local function log(lev, msg)
   if lev <= loglevel then
      log_chat("CGCC: "..msg)
   end
end

-- where am i?
-- GetSystemID(GetCurrentSectorid())
-- LocationStr(GetCurrentSectorid())
-- SystemNames[GetCurrentSystemid()]
-- Return the system and sector name for a given sectorid
local function getSysAndSect(sectorid)
   --LocationStr(5000) -> "Sedina System, Sector H-9"
   log(6, "sectorid = "..sectorid)
   local n = LocationStr(sectorid)
   log(6, "sector name = "..n)
   local sys, sect = n:match("([%w ]+) System, Sector ([%w-%d]+)")
   log(6, "system = "..sys.." sector = "..sect)
   assert((sys and sect),
     "Error: unable to derive location info from "..n.." sector id "..sectorid)
   return sys, sect
end

-- ------------------------------------------------------------------------
-- BOT INFO
-- ------------------------------------------------------------------------
local function scanbots(event, sectorid)
   local sys, sect = getSysAndSect(sectorid)
   generalprint("loading info for system "..sys..", sector "..sect);
   -- this doesn't seem to work
   log(2, "bots in this sector ", GetBotSightedInfoForSector(sectorid))
end
local function scanplay(event, charid)
   --local sys, sect = getSysAndSect(sectorid)
   log(2, "player "..GetPlayerName(charid).." ("..charid..") entered sector");
end
local function scanship(event, charid)
   log(2, "ship entered sector ");
end

-- ------------------------------------------------------------------------
-- TRADE INFO
-- ------------------------------------------------------------------------

-- ------------------------------------------------------------------------
-- Get info about all merchandise for sale at this station
--
local function getmerch()
  for item = 1, GetNumStationMerch() do
     -- { neededlevels = {int l1 .. l5}, int price, string longdesc, float mass, string sortgroup, int volume, string extendeddesc, string type, string meshfile, bool usable, int itemid, string icon }
    local haveitem = GetStationMerchInfo(item);
    log(2, "Station has item: ".. haveitem.name..
                  " @"..haveitem.price..
                  " "..haveitem.itemid..
                  " "..haveitem.type..
                  " "..haveitem.volume);
 end
 for i = 1, GetNumStationDesiredItems(), 1 do
    log(2, "This station wants: "..GetStationDesiredItem(i))
 end
end

-- ------------------------------------------------------------------------
-- MINERAL INFO
-- ------------------------------------------------------------------------
-- get info about the scanned asteroid
--
local lastroid;
local lastscan;
local roids = {};
local function scanroid(event, scaninfo, nodeid, objectid)
   log(3, "scanning asteroid "..nodeid..", "..objectid);
   -- nodeid always seems to be "2"... but it may be significant, so keep it
   if nodeid ~= 2 then
      log(0, "Error: unrecognized nodeid "..nodeid..", skipping")
      return;
   end
   local sys, sect = getSysAndSect(GetCurrentSectorid())

   -- initialize since lua does not autovivify
   if not roids[sys] then roids[sys] = {}; end
   if not roids[sys][sect] then roids[sys][sect] = {}; end
   if not roids[sys][sect][objectid] then
      roids[sys][sect][objectid] = {cooldown = nil,
				    info = nil,
				    update = os.time()};
   end

   -- examine temperature
   local temp = scaninfo:match("Temperature: ([%d.]+) K")
   if temp then
      temp = tonumber(temp)
      -- chop off temp
      scaninfo = scaninfo:gsub("Temperature: ([%d.]+) K", "");
      local tm = os.time(); -- ms to secs
      log(7, "temp is "..temp);
      if not roids[sys][sect][objectid].temp then
	 -- first temp reading
	 roids[sys][sect][objectid].temp = temp;
	 roids[sys][sect][objectid].update = tm;
      elseif roids[sys][sect][objectid] and roids[sys][sect][objectid].temp then
	 log(7, "previously scanned "..(tm - roids[sys][sect][objectid].update)..
	        " seconds ago at temp "..roids[sys][sect][objectid].temp)
	 if (temp < roids[sys][sect][objectid].temp) and
	    (tm - roids[sys][sect][objectid].update) > 10 then
	    -- calculate cooldown as degrees per minute
	    local c = 60 * ((temp - roids[sys][sect][objectid].temp) /
			 (tm - roids[sys][sect][objectid].update));
	    if roids[sys][sect][objectid].cooldown then
	       -- average the new and old results
	       roids[sys][sect][objectid].cooldown = (roids[sys][sect][objectid].cooldown + c) / 2;
	    else
	       roids[sys][sect][objectid].cooldown = c;
	    end
	    log(2, "cooldown rate temp "..temp.." to "..roids[sys][sect][objectid].temp..
		" over "..(tm - roids[sys][sect][objectid].update).." seconds == "..
	        roids[sys][sect][objectid].cooldown)
	    roids[sys][sect][objectid].temp = temp;
	    roids[sys][sect][objectid].update = tm;
	 end
      end
   end

   -- don't say anything when still scanning the same roid
   if lastroid == objectid and lastscan == scaninfo then return; end
   lastroid = objectid; lastscan = scaninfo;

   -- we've seen this one before
   if roids[sys][sect][objectid].info then
      generalprint("Known asteroid #"..objectid..": "..roids[sys][sect][objectid].info..
		   " cooldown "..
		   ((roids[sys][sect][objectid].cooldown and roids[sys][sect][objectid].cooldown) or
		    " unknown").." k/m");
      return;
   end

   -- ore contents
   if not scaninfo:match("Ore:") then
      generalprint("No ore info for asteroid #"..objectid);
      return;
   end

   -- new one
   scaninfo = scaninfo:gsub(" Ore: ", " ");
   scaninfo = scaninfo:gsub("[\r\n]+", " ");
   roids[sys][sect][objectid].info = scaninfo;
   generalprint("Saving info on asteroid #"..objectid);
end

-- ------------------------------------------------------------------------
-- Display asteroid info ui
--
local function populateRoidMatrix(m, roids, sys, sect)
   log(1, "populate matrix");
   m.NUMLIN=0  -- empty out and start over
   -- exit if we have no info about this system or sector
   if not roids[sys] or not roids[sys][sect] then
      log(2, "no info for "..sys..", "..sect)
      return;
   end
   local row = 1;
   local objectid
   for objectid in pairs(roids[sys][sect]) do
      log(3, "add to matrix roid "..objectid.." at row "..row);
      m.ADDLIN = 1
      m[row..":"..1] = objectid;
      m[row..":"..2] = ((roids[sys][sect][objectid].cooldown and
			 string.format("%.2f", roids[sys][sect][objectid].cooldown)) or
		        "?");
      m[row..":"..3] = ((roids[sys][sect][objectid].update and
			 os.date("%d-%b-%y %H:%M:%S", roids[sys][sect][objectid].update)) or
		      "?")
      m[row..":"..4] = roids[sys][sect][objectid].info;
      row = row + 1
   end
end
local roidlist_matrix = iup.matrix{
   numcol=4,
   EXPAND='YES', 
   EDIT_MODE='NO',
   RESIZEMATRIX='YES',
   BGCOLOR='0 0 0 0*',
};
roidlist_matrix:setcell(0,1,'Number');
roidlist_matrix:setcell(0,2,'Cooldown')
roidlist_matrix:setcell(0,3,'Last Update');
roidlist_matrix:setcell(0,4,'Contents');
-- matrix for displaying totals across system
local system_matrix = iup.matrix{
   numcol=4,
   EXPAND='YES', 
   EDIT_MODE='NO',
   RESIZEMATRIX='YES',
   BGCOLOR='0 0 0 0*',
   NUMLIN=265,  -- 16x16
   ALIGNMENT1 = 'ALEFT',
   ALIGNMENT2 = 'ARIGHT',
   ALIGNMENT3 = 'ARIGHT',
};
system_matrix:setcell(0,1,'Sector');
system_matrix:setcell(0,2,'Count');
system_matrix:setcell(0,3,'% Known');
-- drop-downs
-- we need different system lists for each tab
local systemsectlist = iup.stationsublist{dropdown="YES", expand="HORIZONTAL"}
local sectorlist = iup.stationsublist{dropdown="YES", expand="HORIZONTAL"}
local systemlist = iup.stationsublist{dropdown="YES", expand="HORIZONTAL"}

-- set up sector and system lists
do
   local count=1, x, y
   for x = 1, 16 do
      for y = 1, 16 do
	 sectorlist[count]=string.char(64+x).."-"..y
	 count = count + 1
      end
   end
   count = 1
   for x,y in ipairs(SystemNames) do
     systemlist[x] = y
     systemsectlist[x] = y
     count = count + 1
   end
end

local function refreshui()
   log(1, "refreshing UI");
   --local sys, sect = getSysAndSect(GetCurrentSectorid())
   local sys = SystemNames[tonumber(systemsectlist.value)]
   local sect = sectorlist[sectorlist.value]
   log(8, "sector asteroid selection = "..systemsectlist.value..", "..sectorlist.value)
   log(1, "s =  "..systemlist.value)
   log(3, "sector asteroid selection = "..sys..", "..sect)
   -- list of asteroids in this sector
   populateRoidMatrix(roidlist_matrix, roids,
		      sys, sect);
   -- list summary for system
   local count=1
   local x, y
   sys = SystemNames[tonumber(systemlist.value)]
   for x = 1, 16 do
      for y = 1, 16 do
	 local sect = string.char(64+x).."-"..y
	 log(7, "summarizing roid info for "..sys..", "..sect);
	 system_matrix:setcell(count,1,sect);
	 -- count up how many roids we have charted, and the max
	 if not roids[sys] or not roids[sys][sect] then
	    system_matrix:setcell(count,2,"?");
	    system_matrix:setcell(count,3,"0");	    
	 else
	    local c = 0
	    local m = 0
	    local objectid
	    for objectid in pairs(roids[sys][sect]) do
	       log(1, "getting info for roid number "..objectid);
	       if objectid > m then m = objectid end
	       c = c + 1
	    end
	    system_matrix:setcell(count,2,c);
	    system_matrix:setcell(count,3,string.format("%.2f", c/m*100));
	 end
	 count = count + 1
      end
   end
end
function systemlist:action(text,num,value)
   refreshui()
end
function systemsectlist:action(text,num,value)
   refreshui()
end
function sectorlist:action(text,num,value)
   refreshui()
end

-- enclosing dialog box
local roidlist_dialog;
roidlist_dialog = iup.dialog{
   iup.stationhighopacityframe{
      iup.stationhighopacityframebg{
--   iup.pdarootframe {
	 iup.vbox{
	    iup.pda_root_tabs{
	       iup.vbox{
		  tabtitle = "Asteroids in Sector",
		  iup.hbox{
		     iup.label{title='System: ',expand="NO"}, systemsectlist,
		     iup.label{title='Sector: ',expand="NO"}, sectorlist},
		  roidlist_matrix,
	       },
	       iup.vbox{
		  tabtitle = "Asteroids in System",
		  iup.hbox{
		     iup.label{title='System: ',expand="NO"}, systemlist},
		  system_matrix,
	       },
	    },
	    iup.hbox{
	       iup.stationbutton{
		  title='refresh',
		  action = refreshui,
	       },
	       iup.stationbutton{
		  title = 'Close',
		  action = function(self)
			      roidlist_dialog:hide()
			   end
	       }
	    }
	 }
      }
   },
   title = "Asteroid List",
   MENUBOX = 'YES', 
   BORDER = 'YES',
   TOPMOST = 'YES',
   RESIZE = 'YES',
   EXPAND = 'YES',
   size = '%90x%90',
   bgcolor = '0 0 0 0*',
   defaultesc = close,
};
roidlist_dialog:map();
spg=""
-- ------------------------------------------------------------------------
-- LOAD and SAVE database
-- ------------------------------------------------------------------------
-- very simplistic... single user
local function loaddata()
   generalprint("CGCC: loading data");
   HTTP.urlopen(dburl, 'POST',
		function(suc, hd, pg)
		   if hd and hd.status == 200 then
		      log(2, "load sucessful")
		      log(4, "got page data "..pg)
		      spg = pg
		      local r
		      for _,r in ipairs(strsplit("\n", pg)) do
			 log(4, "got line "..r)
			 local f = fromCSV(r)
			 log(4, " split as "..spickle(f))
			 if (f[1] == "roids") then
			    autovivify(roids, f[2], f[3], f[4])
			    if not roids[f[2]][f[3]][f[4]].update or
			       roids[f[2]][f[3]][f[4]].update < tonumber(f[5]) then
			       log(7, "new roid data")
			       roids[f[2]][f[3]][f[4]] = {["update"] = tonumber(f[5]),
							  ["who"] = f[6],
							  ["cooldown"] = tonumber(f[7]),
							  ["temp"] = tonumber(f[8]),
							  ["info"] = f[9]}
			    else
			       log(4, "incoming data for "..f[2].." "..f[3].." "..f[4].." is older than yours, ignoring")
			    end
			 else
			    log(1, "unknown data type "..f[1]);
			 end
		      end
		   else
		      generalprint("load failed!")
		   end
		end, {action = "get"})
end
local lastsave = os.time();
local function savedata()
   -- find updated data
   local upddata = "";
   local sys, sect, objectid
   for sys in pairs(roids) do
      for sect in pairs(roids[sys]) do
	 for objectid in pairs(roids[sys][sect]) do
	    if roids[sys][sect][objectid].update > lastsave then
	       local r = {sys, sect, objectid}
	       --local ore
	       --for ore in orelist do roids[sys][sect][objectid][ore]...
	       upddata = upddata .. toCSV({"roid",
					   sys,sect,objectid,
					   roids[sys][sect][objectid].update,
					   "me",
					   roids[sys][sect][objectid].cooldown,
					   roids[sys][sect][objectid].temp,
					   roids[sys][sect][objectid].info})
	       upddata = upddata .. "\n"
	    end
	 end
      end
   end
   HTTP.urlopen(dburl, 'POST',
		function(suc, hd, pg)
		   if suc then
		      log(2, "save suceeded");
		      lastsave = os.time();
		   end
		end, {action = "save", format = "csv", data = upddata})
end

local function cmd(data, args)
   if args then
      if args[1]:lower() == 'version' then
	 generalprint("some version");
      elseif args[1]:lower() == 'roids' then
	 log(1, "show info about roids");
	 refreshui()
	 PopupDialog(roidlist_dialog, iup.CENTER, iup.CENTER)
      elseif args[1]:lower() == 'test' then
	 systemsectlist.value = args[2]
	 generalprint(systemsectlist.value)
	 generalprint(systemsectlist.text)
      elseif args[1]:lower() == 'dump' then
	 log(1, "dumping all info");
	 local key
	 --for key,val in pairs(roids) do
	 --   generalprint(">> "..key.." -> "..((val.info and val.info) or "no info"));
	 --end
	 --printtable(roids)
	 --generalprint(json.encode(roids));
	 generalprint(spickle(roids))
      elseif args[1]:lower() == 'save' then
	 savedata()
      elseif args[1]:lower() == 'load' then
	 loaddata()
      elseif args[1]:lower() == 'notes' then
	 print(spickle(SystemNotes))
      elseif args[1]:lower() == 'autoscan' then
	 local i = 1;
	 local interval = 1*1000
	 local tm = Timer();
	 local function radarsweep()
	    print("scanning id "..i);
	    radar.SetRadarSelection(2,i);
	    i=i+1
	    if radar.GetRadarSelectionID() then
	       tm:SetTimeout(interval, radarsweep)
	    end
	 end
	 tm:SetTimeout(interval, radarsweep)
      end
   else
      generalprint("Help TBD");
   end
end

local function startup()
   generalprint("CGCC: Loading plugin");
   loaddata()
end

RegisterUserCommand("cgcc", cmd)
--RegisterEvent(getmerch, 'ENTERED_STATION')
RegisterEvent(scanroid, 'TARGET_SCANNED')
RegisterEvent(scanbots, 'SECTOR_CHANGED')
RegisterEvent(scanplay, 'PLAYER_ENTERED_SECTOR')
RegisterEvent(scanship, 'SHIP_SPAWNED')
--RegisterEvent(, 'SYSTEM_BEST_PRICE')
RegisterEvent(savedata, 'PLAYER_LOGGED_OUT')
RegisterEvent(startup, 'PLAYER_ENTERED_GAME') -- starting the game

